#ifndef TGAIMAGE_H
#define TGAIMAGE_H

#include <vector>
#include <cstdint>
#include <fstream>
#include <vector>
#include <array>
#include <memory>

/// TGA file format
/// 
/// TGA v2.0
/// https://www.cnblogs.com/fortunely/p/18022053
/// https://en.wikipedia.org/wiki/Truevision_TGA
/// 
/// https://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf
///
/// TGA v1.0
/// http://www.paulbourke.net/dataformats/tga/
/// https://www.gamers.org/dEngine/quake3/TGA.txt


namespace raster {

#pragma pack(push, 1)
struct TGAHeader {
    uint8_t  idLength;
    uint8_t  colorMapType;
    uint8_t  imageType;
    uint16_t firstEntryIndex;
    uint16_t colorMapLength;
    uint8_t  colorMapEntrySize;
    uint16_t xOrigin;
    uint16_t yOrigin;
    uint16_t width;
    uint16_t height;
    uint8_t  bitsPerPixel;   // Common values: 8, 16, 24, 32
    uint8_t  imageDescriptor; // bit5-4: Image Origin, bit 3-0: Alpha Channel Bits
};
static_assert(sizeof(TGAHeader) == 18, "TGAHeader size is not 18 bytes");

struct TGADevelopArea {
    std::vector<uint8_t> developeFields;
    std::vector<uint8_t> developerDirectory;
};

//  fixed offset and size: 2+41+324+6+41+3+41+3+4+2+2+4+4+4+1=482
static constexpr size_t kTgaExtensionAreaFixedSize = 482;
struct TGAExtensionArea {
    uint16_t extensionSize;
    char     authorName[41];
    char     authorComment[324];
    uint16_t datetimeStamp[6];
    char     jobName[41];
    uint8_t  jobTime[3];
    char     softwareID[41];
    uint8_t  softwareVersion[3];
    uint32_t keyColor[4];
    uint16_t pixelAspectRatio[2];
    uint16_t gammaValue[2];
    uint32_t colorCorrectionOffset;
    uint32_t pastageStampOffset;
    uint32_t scanLineOffset;
    uint8_t  attributeType;
    // to here, fixed offset and size: 2+41+324+6+41+3+41+3+4+2+2+4+4+4+1=482

    std::vector<uint32_t> scanLineTable;
    std::vector<uint8_t> postageStampImage;

    std::array<uint8_t, 1024> colorCorrectionTable;
};
static_assert(sizeof(TGAExtensionArea) >= 1506, "TGAExtensionArea size is less than 1506 bytes");

struct TGAFooter {
    uint32_t extensionOffset;
    uint32_t developerDirectoryOffset;
    char     signature[18];
};
static_assert(sizeof(TGAFooter) == 26, "TGAFileFooter size is not 26 bytes");
#pragma pack(pop)


struct TGAColor {
    union {
        uint8_t  bgra[4];
        uint32_t val;

        // keep the same order with TGA channel's order of a pixel for image data
        // MSB -> LSB <=> (low) A R G B (high)
        // https://www.cnblogs.com/fortunely/p/18022053
        struct { uint8_t b, g, r, a; };
    };
    uint8_t  bytespp;
#if 1
    uint8_t& operator[](const int i) { return bgra[i]; }
#else // equivalent
    uint8_t& operator[](const int i) {
        switch(i) {
        case 0: return b;
        case 1: return g;
        case 2: return r;
        case 3: return a;
        default: return a;
        }
    }
#endif

    TGAColor() : bgra{0, 0, 0, 0}, bytespp(1) {}
    TGAColor(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b), a(255), bytespp(3) {}
    TGAColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a), bytespp(4) {}
    TGAColor(uint8_t v) : bgra{v, v, v, v}, bytespp(1) {}
    TGAColor(const uint8_t* p, uint8_t bpp) : bytespp(bpp) {
        for (int i = 0; i < static_cast<int>(bpp); i++) {
            bgra[i] = p[i];
        }
        for (int i = bpp; i < 4; i++) {
            bgra[i]= 0;
        }
    }
    TGAColor operator*(float intensity) const {
        TGAColor res = *this;
        intensity = ((intensity > 1.0f) ? 1.0f : (intensity < 0.0f) ? 0.0f : intensity);
        for (int i = 0; i < 4; i++) {
            res.bgra[i] = static_cast<uint8_t>(bgra[i] * intensity);
        }
        return res;
    }
};

#define kBlackColor   TGAColor{0,   0,   0,   255}
#define kWhiteColor   TGAColor{255, 255, 255, 255}
#define kRedColor     TGAColor{255, 0,   0,   255}
#define kGreenColor   TGAColor{0,   255, 0,   255}
#define kBlueColor    TGAColor{0,   0,   255, 255}


class TGAImage {
public:
    enum Format {
        GRAYSCALE = 1,
        RGB = 3,
        RGBA = 4,
    };
    
    TGAImage();
    TGAImage(int w, int h, int bpp);
    TGAImage(const TGAImage& other) = delete;
    ~TGAImage();
    TGAImage& operator=(const TGAImage& other) = delete;

    bool readTgaFile(const char* filename);
    bool writeTgaFile(const char* filename, bool vflip = true, bool rle = true);

    TGAColor get(int x, int y) const;
    void set(int x, int y, const TGAColor& c);
    void flipHorizontally();
    void flipVertically();

    int width() const { return w; }
    int height() const { return h; }
    int bytespp() const { return bpp; }
    int version() const { return tgaVerion; }

private:
    bool readHeader(std::ifstream& in);
    bool writeHeader(std::ofstream &out, bool vflip = true, bool rle = true);
    bool readData(std::ifstream& in);
    bool writeData(std::ofstream& out, bool rle = true);
    bool readDevelopArea(std::ifstream& in);
    bool writeDevelopArea(std::ofstream& out);
    bool readExtensionArea(std::ifstream& in);
    bool writeExtensionArea(std::ofstream& out);
    bool readFooter(std::ifstream& in);
    bool writeFooter(std::ofstream& out);

    bool loadRleData(std::ifstream& in);
    bool unloadRleData(std::ofstream& out);

    // tga properties
    std::shared_ptr<TGAHeader> header;
    std::shared_ptr<TGAExtensionArea> extensionArea;
    std::shared_ptr<TGADevelopArea> developeArea;
    std::shared_ptr<TGAFooter> footer;
    int tgaVerion = 1;

    // image properties
    int w = 0; // width
    int h = 0; // height
    int bpp = 0; // bytes per pixel
    std::vector<uint8_t> data{};

    // file properties
    std::string filePath;

    // footer signature
    static constexpr char kFooterSignatureV1[] = ".";
    static constexpr char kFooterSignatureV2[] = "TRUEVISION-XFILE.";
};

} // namespace

#endif
